package com.domain.assembler;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.domain.DO.FlightClass;
import com.domain.DO.FlightInfo;
import com.domain.DTO.FlightDto;
import com.domain.DTO.FlightInfoDto;
import com.domain.VO.BestFlightChoseRoleVo;
import com.domain.VO.FlightChoseRoleVo;
import com.domain.pojo.CityNode;
import com.mapper.FlightClassMapper;
import com.mapper.FlightInfoMapper;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.domain.factory.FlightInfoFactory.FLIGHT_INFO_CONVERT;


/**
 * @author 大忽悠
 * @create 2022/11/10 20:13
 */
@Component
@RequiredArgsConstructor
public class FlightAssembler {
    private final FlightClassMapper flightClassMapper;
    private final FlightInfoMapper flightInfoMapper;

    /**
     * 1.确定选择策略: 时间还是金钱<br>
     * 2.依据策略进行排序,然后选择出最佳方案
     */
    public FlightInfoDto filterBestRouter(BestFlightChoseRoleVo bestFlightChoseRoleVo) {
        List<FlightInfoDto> flightInfoDtos = filterFlightByChoseRole(bestFlightChoseRoleVo);
        return bestFlightChoseRoleVo.getBestFlight(flightInfoDtos);
    }

    /**
     * 要考虑转机情况 <br>
     * 1.先构造出路线图 <br>
     * 2.通过递归搜索,找出所有解
     */
    public List<FlightInfoDto> filterFlightByChoseRole(FlightChoseRoleVo flightChoseRoleVo) {
        //1.以用户指定的出发城市为根构建图
        CityNode rootCityNode = buildGraph(flightChoseRoleVo.getCityLeave());
        //没有查询到有效航班
        if (rootCityNode == null) {
            return null;
        }
        //2.根据用户选择目标城市搜索图,找到所有可达路径
        List<String> allAvailableRouter = searchGraph(rootCityNode, flightChoseRoleVo.getCityArrive());
        //无有效路径
        if (ObjectUtils.isEmpty(allAvailableRouter)) {
            return null;
        }
        //3.拼接返回结果信息---考虑班次
        return buildFlightDtoInfoList(allAvailableRouter, flightChoseRoleVo.getTimeLeave(),flightChoseRoleVo.getPoorSeat());
    }

    /**
     * 1->2->3 <br>
     * 1号班次起飞时间要晚于用户出发时间,2号班次起飞时间要晚于1号班次到达时间 <br>
     * 晚的最大限制为24小时
     */
    private List<FlightInfoDto> buildFlightDtoInfoList(List<String> allAvailableRouter, LocalDateTime timeLeave, Boolean poorSeat) {
        List<FlightDto> flightDtoList = new ArrayList<>(allAvailableRouter.size());
        //还需要排除掉这条航行虽然存在,但是不存在匹配用户出发时间的航行
        for (String availableRouter : allAvailableRouter) {
            //如果起点存在多个班次,那么需要将每个班次单独封装为一个FlightDto
            flightDtoList.addAll(Objects.requireNonNull(doBuildOneFlightDto(availableRouter.split("->"), 0, timeLeave,poorSeat)));
        }
        //计算每条路径总耗时和总费用
        return flightDtoList.stream().map(FlightInfoDto::wrapperFlightDto).collect(Collectors.toList());
    }

    private List<FlightDto> doBuildOneFlightDto(String[] flightIdList, Integer index, LocalDateTime timeLeave, Boolean poorSeat) {
        if (index == flightIdList.length) {
            return null;
        }
        //每条航班对应多个班次
        List<FlightDto> flightDtoList = new ArrayList<>();
        //当前航班对应的所有班次
        List<FlightClass> flightClasses = flightClassMapper.selectList(new QueryWrapper<FlightClass>().eq("flight_id", flightIdList[index]));

        //根据出发时间进行过滤: 如果某个班次飞机起飞时间比出发时间早--排除
        //飞机起飞时间晚于出发时间24小时--排除
        List<FlightClass> validFlightClassList = flightClasses.stream().filter(flightClass -> {
            LocalDateTime flyTime = flightClass.getDepartureTime();
            if (flyTime.isBefore(timeLeave)) {
                return Boolean.FALSE;
            }
            return Duration.between(timeLeave, flyTime).toHours() < 24L;
        }).collect(Collectors.toList());

        //如果发现没有符合要求的班次,那么这趟航线取消
        if (ObjectUtils.isEmpty(validFlightClassList)) {
            return Collections.emptyList();
        }

        //根据剩余座位进行过滤
        validFlightClassList = validFlightClassList.stream().filter(flightClass -> poorSeat ? flightClass.getPoorLeftNumber() > 0 : flightClass.getRichLeftNumber() > 0).collect(Collectors.toList());

        //如果发现没有符合要求的班次,那么这趟航线取消
        if (ObjectUtils.isEmpty(validFlightClassList)) {
            return Collections.emptyList();
        }

        //班次有效班次列表,组装信息
        validFlightClassList.forEach(flightClass -> {
            FlightDto flightDto = FLIGHT_INFO_CONVERT.toFlightDto(flightInfoMapper.selectById(flightIdList[index]), flightClass);
            //下一次转机的飞机起飞时间必须是晚于当前班次飞机到达时间的
            List<FlightDto> validFlightDtoList = doBuildOneFlightDto(flightIdList, index + 1,flightClass.getArrivalTime(), poorSeat);
            //如果得到的下一趟航班列表为空,说明此航线无符合要求班次,取消
            if (validFlightDtoList == null || !validFlightDtoList.isEmpty()) {
                flightDto.setNext(validFlightDtoList);
                flightDtoList.add(flightDto);
            }
        });

        return flightDtoList;
    }

    private List<String> searchGraph(CityNode rootCityNode, String cityArrive) {
        //保存航班顺序: 1->2->3->4 ,用->进行分割
        List<String> allAvailableRouter = new ArrayList<>();
        //遍历根节点下的子节点列表
        for (CityNode.CityNodeWrapper cityNodeWrapper : rootCityNode.getNextCityNodeList()) {
            //对每个子节点进行递归搜索
            CityNode cityNode = cityNodeWrapper.getCityNode();
            //递归结束条件: 当前节点下无子节点或者递归过程中遇到和自己同名的子节点,说明有环路,结束此次递归
            doSearchGraph(cityNodeWrapper, cityArrive, cityNode.getCityName(), Boolean.TRUE, allAvailableRouter, String.valueOf(cityNodeWrapper.getFlightId()));
        }
        return allAvailableRouter;
    }


    private void doSearchGraph(CityNode.CityNodeWrapper cityNode, String cityArrive, String rootCityName, Boolean first, List<String> allAvailableRouter, String router) {
        if (cityNode == null || (!first && cityNode.getCityNode().getCityName().equals(rootCityName))) {
            return;
        }
        if (cityNode.getCityNode().getCityName().equals(cityArrive)) {
            allAvailableRouter.add(router);
            return;
        }
        List<CityNode.CityNodeWrapper> nextCityNodeList = cityNode.getCityNode().getNextCityNodeList();
        if (ObjectUtils.isEmpty(nextCityNodeList)) {
            return;
        }
        //遍历子节点
        for (CityNode.CityNodeWrapper cityNodeWrapper : nextCityNodeList) {
            //对每个子节点进行递归搜索
            String tempRouter = router;
            router += "->" + cityNodeWrapper.getFlightId();
            //递归结束条件: 当前节点下无子节点或者递归过程中遇到和自己同名的子节点,说明有环路,结束此次递归
            doSearchGraph(cityNodeWrapper, cityArrive, rootCityName, Boolean.FALSE, allAvailableRouter, router);
            router = tempRouter;
        }
    }

    private CityNode buildGraph(String userCityLeave) {
        //1.查询出所有航班
        List<FlightInfo> flightInfoList = flightInfoMapper.selectList(null);
        //2.按照出发城市分组
        Map<String, List<FlightInfo>> groupByCityLeave = flightInfoList.stream().collect(Collectors.groupingBy(FlightInfo::getDepartureCity));
        //3.获取以用户指定出发城市开始的记录
        List<FlightInfo> userFlightInfoList = groupByCityLeave.get(userCityLeave);
        //3.1 没有航班从当前用户城市起飞
        if (ObjectUtils.isEmpty(userFlightInfoList)) {
            return null;
        }
        //3.2 以当前用户出发城市为根节点,构建一颗多叉树
        CityNode rootCityNode = CityNode.builder().cityName(userCityLeave).build();
        Set<String> waitForFilter=new HashSet<>();
        waitForFilter.add(userCityLeave);
        buildCityTree(rootCityNode, userFlightInfoList, groupByCityLeave, waitForFilter);
        return rootCityNode;
    }

    /**
     * 以传入的出发城市为根,递归构建一颗树,结束构建的条件是: 没有以当前城市作为起飞城市的航班,或者目的城市与用户设置的起飞城市一致
     *
     * @param cityNode
     * @param flightInfos      从当前城市出发的航班列表
     * @param groupByCityLeave
     * @param userCityLeave
     * @return 返回根节点
     */
    private void buildCityTree(CityNode cityNode, List<FlightInfo> flightInfos, Map<String, List<FlightInfo>> groupByCityLeave, Set<String> userCityLeave) {
        if (ObjectUtils.isEmpty(flightInfos)) {
            return;
        }
        //从当前城市出发的航班列表
        for (FlightInfo flightInfo : flightInfos) {
            if (userCityLeave.contains(flightInfo.getArriveCity())) {
                continue;
            }
            CityNode nextCityNode = cityNode.addNextCityNode(flightInfo.getArriveCity(), flightInfo.getId());
            //递归构建上面创建的子节点
            userCityLeave.add(nextCityNode.getCityName());
            buildCityTree(nextCityNode, groupByCityLeave.get(nextCityNode.getCityName()), groupByCityLeave,userCityLeave);
            userCityLeave.remove(nextCityNode.getCityName());
        }
    }
}
